﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Sustainalytics.Entities;
using System.Security.Claims;

namespace Sustainalytics.Claims
{
    public class ExcelClaimsModifier
    {
        private readonly IOperationClaimsRetriever _operationClaimsRetriever;
        private readonly Func<Guid, Account> _accountRetriever;

        private const string _issuer = "Sustainalytics";
        private ImportLogs ImportLogs;
        private StringBuilder Errors;

        public ExcelClaimsModifier(IOperationClaimsRetriever operationsClaimsRetriever, Func<Guid, Account> accountRetriever)
        {
            _operationClaimsRetriever = operationsClaimsRetriever;
            _accountRetriever = accountRetriever;
        }

        public ImportLogs ParseExcel(byte[] dataFile, bool processAll = false)
        {
            Errors = new StringBuilder();

            SpreadsheetGear.IWorkbook book = SpreadsheetGear.Factory.GetWorkbookSet().Workbooks.OpenFromMemory(dataFile);

            if (processAll)
            {
                Entities.Claims claims = new Entities.Claims();

                claims.ClaimsMap = ProcessClaimsSheet(book);

                claims.UserClaims = ProcessUsersSheet(book);

                _operationClaimsRetriever.AddClaims(claims);
            }

            _operationClaimsRetriever.AddResourceMap(ProcessMapSheet(book));

            _operationClaimsRetriever.ClearMemoryCache();

            return ImportLogs = new ImportLogs() { Errors = Errors.ToString() };
        }

        #region Privates - Excel Page Processing
       
        private List<ClaimMap> ProcessClaimsSheet(SpreadsheetGear.IWorkbook book)
        {
            List<ClaimMap> claimMaps = new List<ClaimMap>();

            var claimsData = book.Worksheets[0].Cells;
            int headerIndex = 0;
            var processedRows = 0;

            // process Claims Sheet
            for (var rowIndex = headerIndex + 1; ; rowIndex++)
            {
                // ignore row if a cell is empty
                if ((!string.IsNullOrEmpty(claimsData[rowIndex, 0].Text)) && (!string.IsNullOrEmpty(claimsData[rowIndex, 1].Text)) && (!string.IsNullOrEmpty(claimsData[rowIndex, 2].Text)))
                {
                    ClaimMap claimMap = new ClaimMap();
                    claimMap.Id = Guid.NewGuid();
                    claimMap.Permission = claimsData[rowIndex, 0].Text;
                    claimMap.Claims = new List<Claim>();

                    claimMap.Claims.Add(new Claim(claimsData[rowIndex, 1].Text, claimsData[rowIndex, 2].Text, ClaimValueTypes.String, _issuer));

                    claimMaps.Add(claimMap);
                }
                else
                {
                    // stop if a row is empty
                    if ((string.IsNullOrEmpty(claimsData[rowIndex, 0].Text)) && (string.IsNullOrEmpty(claimsData[rowIndex, 1].Text)) && (string.IsNullOrEmpty(claimsData[rowIndex, 2].Text)))
                    {

                        break;
                    }
                    else
                    {
                        if (string.IsNullOrEmpty(claimsData[rowIndex, 0].Text))
                            Errors.Append(string.Format("Claims - Mandatory row {1} on line {0} is empty \r", rowIndex + 1, 0));

                        if (string.IsNullOrEmpty(claimsData[rowIndex, 1].Text))
                            Errors.Append(string.Format("Claims - Mandatory row {1} on line {0} is empty \r", rowIndex + 1, 1));

                        if (string.IsNullOrEmpty(claimsData[rowIndex, 2].Text))
                            Errors.Append(string.Format("Claims - Mandatory row {1} on line {0} is empty \r", rowIndex + 1, 1));

                        processedRows++;
                    }
                }

            }

            return claimMaps;
        }

        private enum UserClaimsIndexes
        {
            DescriptionIndex = 0,
            AccountNameIndex,
            AccountIdIndex,
            ClaimNameIndex,
            ClaimValueIndex,
            LastIndex = ClaimValueIndex
        }

        private const int _headerIndex = 0;

        private List<UserClaim> ProcessUsersSheet(SpreadsheetGear.IWorkbook book)
        {
            List<UserClaim> userClaims = new List<UserClaim>();

            var claimsData = book.Worksheets[1].Cells;
            
            var processedRows = 0;

            if (!IsColumnsNoRight(claimsData))
                return userClaims;

            // process Users Sheet
            for (var rowIndex = _headerIndex + 1; ; rowIndex++)
            {
                var accName = claimsData[rowIndex, (int)UserClaimsIndexes.AccountNameIndex].Text;
                var accId = claimsData[rowIndex, (int)UserClaimsIndexes.AccountIdIndex].Text;
                var clName = claimsData[rowIndex, (int)UserClaimsIndexes.ClaimNameIndex].Text;
                var clValue = claimsData[rowIndex, (int)UserClaimsIndexes.ClaimValueIndex].Text;

                // ignore row if a cell is empty
                if ((!string.IsNullOrEmpty(accName))
                    && (!string.IsNullOrEmpty(accId))
                    && (!string.IsNullOrEmpty(clName))
                    && (!string.IsNullOrEmpty(clValue)))
                {
                    Guid id;
                    if (!Guid.TryParse(accId, out id))
                    {
                        Errors.Append(string.Format("Users - cell {1} on line {0} should be a GUID \r", rowIndex + 1, (int)UserClaimsIndexes.AccountIdIndex));
                        continue;
                    }

                    if (!IsNoMatchBetweenAccountNameAndID(accName, id))
                    {
                        Errors.Append(string.Format("Users - There is a mismatch between AccountId and AccountName on line {0}. Line ignored!\r", rowIndex + 1));
                        continue;
                    }

                    var userClaim = userClaims.FirstOrDefault(uc => uc.Account == accName && uc.Id == id);
                    if (userClaim == null)
                    {
                        userClaim = new UserClaim
                        {
                            Account = accName,
                            Id = id,
                            Claims = new List<Claim>
                            {
                                new Claim(clName, clValue, ClaimValueTypes.String, _issuer)
                            }
                        };

                        userClaims.Add(userClaim);
                    }
                    else
                    {
                        userClaim.Claims.Add(new Claim(clName, clValue, ClaimValueTypes.String, _issuer));
                    }
                }
                else
                {
                    // stop if a row is empty
                    if ((string.IsNullOrEmpty(accName))
                        && (string.IsNullOrEmpty(accId))
                        && (string.IsNullOrEmpty(clName))
                        && (string.IsNullOrEmpty(clValue)))
                    {

                        break;
                    }
                    else
                    {
                        CheckEmptyCell(accName, rowIndex, (int)UserClaimsIndexes.AccountNameIndex);
                        CheckEmptyCell(accId, rowIndex, (int)UserClaimsIndexes.AccountIdIndex);
                        CheckEmptyCell(clName, rowIndex, (int)UserClaimsIndexes.ClaimNameIndex);
                        CheckEmptyCell(clValue, rowIndex, (int)UserClaimsIndexes.ClaimValueIndex);
                        processedRows++;
                    }
                }

            }

            return userClaims;
        }

        private bool IsColumnsNoRight(SpreadsheetGear.IRange claimsData)
        {
            if (string.IsNullOrEmpty(claimsData[_headerIndex, (int)UserClaimsIndexes.LastIndex].Text))
            {
                Errors.Append("Users: Invalid Excel format. 5 columns should be present: Description, Account Name, Account Id, Claims, Value.\r");
                return false;
            }
            return true;
        }

        private bool IsNoMatchBetweenAccountNameAndID(string accName, Guid id)
        {
            var acc = _accountRetriever.Invoke(id);
            return acc != null && acc.Name.Trim() == accName.Trim();
        }

        private void CheckEmptyCell(string cellText, int rowIndex, int colIndex)
        {
            if (string.IsNullOrEmpty(cellText))
                Errors.Append(string.Format("Users - Mandatory cell {1} on line {0} is empty \r", rowIndex + 1, colIndex));
        }

        private List<ResourceMap> ProcessMapSheet(SpreadsheetGear.IWorkbook book)
        {
            List<ResourceMap> resourceMaps = new List<ResourceMap>();

            int headerIndex = 0;
            var processedRows = 0;
            var claimsData = book.Worksheets[2].Cells;

            for (var rowIndex = headerIndex + 1; ; rowIndex++)
            {
                // ignore row if a cell is empty other than the parameters cell
                if ((!string.IsNullOrEmpty(claimsData[rowIndex, 0].Text)) && (!string.IsNullOrEmpty(claimsData[rowIndex, 1].Text)) && (!string.IsNullOrEmpty(claimsData[rowIndex, 2].Text)))
                {
                    ResourceMap resourceMap = new ResourceMap();

                    resourceMap.Permission = claimsData[rowIndex, 0].Text;
                    resourceMap.Resource = new Resource() { Name = claimsData[rowIndex, 1].Text };
                    resourceMap.Value = claimsData[rowIndex, 2].Text;

                    if (!string.IsNullOrEmpty(claimsData[rowIndex, 3].Text))
                        resourceMap.Parameters = ParseParameters(claimsData[rowIndex, 3].Text);

                    resourceMaps.Add(resourceMap);
                    processedRows++;
                }
                else
                {
                    // stop if a row is empty
                    if ((string.IsNullOrEmpty(claimsData[rowIndex, 0].Text)) && (string.IsNullOrEmpty(claimsData[rowIndex, 1].Text)) && (string.IsNullOrEmpty(claimsData[rowIndex, 2].Text)))
                    {
                        if (processedRows == 0)
                        {
                            Errors.Append(string.Format("Map - Mandatory row {0} on column {1} is empty \r" +
                                                    "Mandatory row {2} on column {3} is empty \r" +
                                                    "Mandatory row {4} on column {5} is empty \r",
                                                    rowIndex + 1, 0, rowIndex + 1, 1, rowIndex + 1, 2));
                        }
                        break;
                    }
                    else
                    {
                        if (string.IsNullOrEmpty(claimsData[rowIndex, 0].Text))
                            Errors.Append(string.Format("Map - Mandatory row {0} on column {1} is empty \r", rowIndex + 1, 0));

                        if (string.IsNullOrEmpty(claimsData[rowIndex, 1].Text))
                            Errors.Append(string.Format("Map - Mandatory row {0} on column {1} is empty \r", rowIndex + 1, 1));

                        if (string.IsNullOrEmpty(claimsData[rowIndex, 2].Text))
                            Errors.Append(string.Format("Map - Mandatory row {0} on column {1} is empty \r", rowIndex + 1, 2));

                        processedRows++;
                    }
                }

            }

            return resourceMaps;
        }

        private List<ResourceParameters> ParseParameters(string parameters)
        {
            List<ResourceParameters> param = new List<ResourceParameters>();
            string[] _parameters = parameters.Split(',');
            foreach (string value in _parameters)
            {
                string[] _value = value.Split('=');

                param.Add(new ResourceParameters() { Key = _value[0], Value = _value[1] });
            }

            return param;
        }

        #endregion

    }
}
